#pragma once

#include "Log.hpp"
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <string>
#include <errno.h>
#include <strings.h> //bzero
#include <netinet/in.h> //struct sockaddr_in
#include <arpa/inet.h>
#include <functional>
#include "Inet_Addr.hpp"

using func_t = std::function<void(int, const std::string&, const Inet_Addr&)>;//包装一切参数和返回值相同的可调用对象: 函数指针, lambda, 仿函数...

class server_udp
{
private:
    void init()
    {
        //1.创建socket
        _socket_fd = socket(AF_INET, SOCK_DGRAM, 0);//前两个参数组合代表UDP协议通信
        if(_socket_fd < 0)
        {
            LOG(wzx::log_level::FATAL) << "socket construct failed!!!";
            exit(errno);
        }
        LOG(wzx::log_level::INFO) << "socket construct successfully, _socket_fd: " << _socket_fd;

        //2.绑定信息
        //2.1.创建并设置sockaddr结构
        struct sockaddr_in tmp;
        bzero(&tmp, sizeof(tmp));//指定空间清零
        tmp.sin_family = AF_INET;
        tmp.sin_port = htons(_port);//主机字节序 -> 网络字节序
        // tmp.sin_addr.s_addr = inet_addr(_ip.c_str());//1.字符串 -> 32位, 2.主~ -> 网~
        tmp.sin_addr.s_addr = INADDR_ANY/*0*/; //任意IP绑定, 可以接受任意IP的client发送的报文

        //2.2.绑定至内核
        int ret_bind = bind(_socket_fd, (const sockaddr*)&tmp, sizeof(tmp));//用于服务端绑定socket
        if(ret_bind != 0)
        {
            LOG(wzx::log_level::FATAL) << "bind failed!!!";
            exit(errno);
        }
        LOG(wzx::log_level::INFO) << "bind successfully...";
    }


public:
    server_udp(uint16_t port, func_t handle_func)
    :_port(port)
    // ,_ip(ip)
    ,_handle_func(handle_func)
    ,_running_tag(false)
    {
        init();
    }

    #define DEFAULT_BUFFER 1024
    void start()
    {
        _running_tag = true;

        char buffer[DEFAULT_BUFFER];//缓冲区
        //ps: 循环内外声明变量会对效率有影响吗?
        //  1.对于基本类型，循环内外声明变量在效率上通常没有本质区别，因为编译器会优化。
        //  2.对于非基本类型，建议在循环外部声明，以避免重复构造和析构带来的开销。
        //  3.从代码风格上讲，尽量缩小变量的作用域，避免不必要的变量暴露在外，以提高代码的可维护性和减少错误。

        while(_running_tag)//服务器永不退出
        {
            //1.收消息
            struct sockaddr_in peer;
            socklen_t peer_len = sizeof(peer);//每次进循环都要更新, 因为它同时为输入和输出参数, 会变的
            ssize_t recv_byte = recvfrom(_socket_fd, buffer, sizeof(buffer) - 1, 0/*阻塞IO*/, (struct sockaddr*)&peer, &peer_len);
            if(recv_byte > 0)
            {
                buffer[recv_byte] = '\0';//字符串结尾加0, 否则读时会越界 产生乱码

                //2.处理消息 -- 交给路由器(发送消息也交给路由器)
                Inet_Addr user(peer);
                _handle_func(_socket_fd, buffer, user);

            }
        }
    }


private:
    int _socket_fd;//目前认为: 等同于文件描述符 且同时支持读写
    uint16_t _port;
    // std::string _ip; --> INADDR_ANY

    bool _running_tag;
    func_t _handle_func;//可以接受一切参数和返回值相同的可调用对象: 函数指针, lambda, 仿函数...
};